//
// Copyright 2021-2022 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//

use neon::prelude::*;
use paste::paste;
use std::cell::RefCell;
use std::collections::hash_map::DefaultHasher;
use std::convert::TryInto;
use std::hash::Hasher;
use std::ops::{Deref, DerefMut, RangeInclusive};
use std::slice;

use crate::support::{Array, FixedLengthBincodeSerializable, Serialized};

use super::*;

/// Converts arguments from their JavaScript form to their Rust form.
///
/// `ArgTypeInfo` has two required methods: `borrow` and `load_from`. The use site looks like this:
///
/// ```no_run
/// # use libsignal_bridge::node::*;
/// # use neon::prelude::*;
/// # struct Foo;
/// # impl SimpleArgTypeInfo for Foo {
/// #     type ArgType = JsObject;
/// #     fn convert_from(cx: &mut FunctionContext, _: Handle<JsObject>) -> NeonResult<Self> {
/// #         Ok(Foo)
/// #     }
/// # }
/// # fn test<'a>(cx: &mut FunctionContext<'a>, js_arg: Handle<'a, JsObject>) -> NeonResult<()> {
/// let mut js_arg_borrowed = Foo::borrow(cx, js_arg)?;
/// let rust_arg = Foo::load_from(&mut js_arg_borrowed);
/// #     Ok(())
/// # }
/// ```
///
/// The `'context` lifetime allows for borrowed values to depend on the current JS stack frame;
/// that is, they can be assured that referenced objects will not be GC'd out from under them.
///
/// `ArgTypeInfo` is used to implement the `bridge_fn` macro, but can also be used outside it.
///
/// If the Rust type can be directly loaded from `ArgType` with no local storage needed,
/// implement [`SimpleArgTypeInfo`] instead.
pub trait ArgTypeInfo<'storage, 'context: 'storage>: Sized {
    /// The JavaScript form of the argument (e.g. `JsNumber`).
    type ArgType: neon::types::Value;
    /// Local storage for the argument (ideally borrowed rather than copied).
    type StoredType: 'storage;
    /// "Borrows" the data in `foreign`, usually to establish a local lifetime or owning type.
    fn borrow(
        cx: &mut FunctionContext<'context>,
        foreign: Handle<'context, Self::ArgType>,
    ) -> NeonResult<Self::StoredType>;
    /// Loads the Rust value from the data that's been `stored` by [`borrow()`](Self::borrow()).
    fn load_from(stored: &'storage mut Self::StoredType) -> Self;
}

/// Converts arguments from their JavaScript form and saves them for use in an `async` function.
///
/// `AsyncArgTypeInfo` works very similarly to `ArgTypeInfo`, but with the added restriction that
/// the stored type is `'static` so that it can be used in an `async` closure. Additionally, the
/// stored type implements `[neon::prelude::Finalize]` so that it can be cleaned up in a JavaScript
/// context. This allows storing persistent references to JavaScript objects.
///
/// Conceptually, the use site for `AsyncArgTypeInfo` looks like this:
///
/// ```no_run
/// # use libsignal_bridge::node::*;
/// # use neon::prelude::*;
/// # extern crate signal_neon_futures;
/// # struct Foo;
/// # impl SimpleArgTypeInfo for Foo {
/// #     type ArgType = JsObject;
/// #     fn convert_from(cx: &mut FunctionContext, _: Handle<JsObject>) -> NeonResult<Self> {
/// #         Ok(Foo)
/// #     }
/// # }
/// # fn test<'a>(mut cx: FunctionContext<'a>, js_arg: Handle<'a, JsObject>) -> NeonResult<()> {
/// // DO NOT COPY THIS CODE - DOES NOT HANDLE ERRORS CORRECTLY
/// let mut js_arg_stored = Foo::save_async_arg(&mut cx, js_arg)?;
/// let promise = signal_neon_futures::promise(&mut cx, async move {
///     let rust_arg = Foo::load_async_arg(&mut js_arg_stored);
///     // ...
///     signal_neon_futures::settle_promise(|cx| {
///         js_arg_stored.finalize(cx);
///         // ...
///         # Ok(cx.undefined())
///     })
/// })?;
/// #     Ok(())
/// # }
/// ```
///
/// The full implementation generated by `bridge_fn` will correctly finalize local storage when
/// there are errors as well. It is not recommended to use `AsyncArgTypeInfo` manually.
///
/// If the Rust type can be directly loaded from `ArgType` with no extra local storage needed,
/// implement [`SimpleArgTypeInfo`] instead.
pub trait AsyncArgTypeInfo<'storage>: Sized {
    /// The JavaScript form of the argument (e.g. `JsNumber`).
    type ArgType: neon::types::Value;
    /// Local storage for the argument that can outlive the current JavaScript context.
    type StoredType: 'static + Finalize;
    /// Saves the data in `foreign` so that it can be used in an `async` context.
    fn save_async_arg(
        cx: &mut FunctionContext,
        foreign: Handle<Self::ArgType>,
    ) -> NeonResult<Self::StoredType>;
    /// Loads the Rust value from the data that's been `stored` by
    /// [`save_async_arg()`](Self::save_async_arg()).
    fn load_async_arg(stored: &'storage mut Self::StoredType) -> Self;
}

/// A simpler interface for [`ArgTypeInfo`] and [`AsyncArgTypeInfo`] for when no separate local
/// storage is needed.
///
/// This trait is easier to use when writing Neon functions manually:
///
/// ```no_run
/// # use libsignal_bridge::node::*;
/// # use neon::prelude::*;
/// # struct Foo;
/// impl SimpleArgTypeInfo for Foo {
///     type ArgType = JsObject;
///     fn convert_from(cx: &mut FunctionContext, _: Handle<JsObject>) -> NeonResult<Self> {
///         // ...
///         # Ok(Foo)
///     }
/// }
///
/// # fn test<'a>(mut cx: FunctionContext<'a>, js_arg: Handle<'a, JsObject>) -> NeonResult<()> {
/// let rust_arg = Foo::convert_from(&mut cx, js_arg)?;
/// #     Ok(())
/// # }
/// ```
///
/// However, some types do need the full flexibility of `ArgTypeInfo` or `AsyncArgTypeInfo`.
pub trait SimpleArgTypeInfo: Sized + 'static {
    /// The JavaScript form of the argument (e.g. `JsNumber`).
    type ArgType: neon::types::Value;
    /// Converts the data in `foreign` to the Rust type.
    fn convert_from(cx: &mut FunctionContext, foreign: Handle<Self::ArgType>) -> NeonResult<Self>;
}

impl<'a, T> ArgTypeInfo<'a, 'a> for T
where
    T: SimpleArgTypeInfo,
{
    type ArgType = T::ArgType;
    type StoredType = Option<Self>;
    fn borrow(
        cx: &mut FunctionContext<'a>,
        foreign: Handle<'a, Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        Ok(Some(Self::convert_from(cx, foreign)?))
    }
    fn load_from(stored: &'a mut Self::StoredType) -> Self {
        stored.take().expect("should only be loaded once")
    }
}

impl<'a, T> AsyncArgTypeInfo<'a> for T
where
    T: SimpleArgTypeInfo,
{
    type ArgType = T::ArgType;
    type StoredType = DefaultFinalize<Option<Self>>;
    fn save_async_arg(
        cx: &mut FunctionContext,
        foreign: Handle<Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        Ok(DefaultFinalize(Some(Self::convert_from(cx, foreign)?)))
    }
    fn load_async_arg(stored: &'a mut Self::StoredType) -> Self {
        stored.0.take().expect("should only be loaded once")
    }
}

// Implement AsyncArgTypeInfo for a slice of SessionRecords outside of
// the node_bridge_handle macro since we don't want to use async for
// the HsmEnclave module.
// FIXME: This is necessarily a cloning API.
// We should try to avoid cloning data when possible.
impl<'a> AsyncArgTypeInfo<'a> for &'a [SessionRecord] {
    type ArgType = JsArray;
    type StoredType = DefaultFinalize<Vec<SessionRecord>>;
    fn save_async_arg(
        cx: &mut FunctionContext,
        array: Handle<Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        let len = array.len(cx);
        let result = (0..len)
            .map(|i| {
                let wrapper: Handle<JsObject> = array.get(cx, i)?;
                let value_box: Handle<DefaultJsBox<std::cell::RefCell<SessionRecord>>> =
                    wrapper.get(cx, NATIVE_HANDLE_PROPERTY)?;
                let cell: &std::cell::RefCell<_> = &***value_box;
                let result = cell.borrow().clone();
                Ok(result)
            })
            .collect::<NeonResult<_>>()?;
        Ok(DefaultFinalize(result))
    }
    fn load_async_arg(stored: &'a mut Self::StoredType) -> Self {
        &stored.0
    }
}

/// Converts result values from their Rust form to their JavaScript form.
///
/// `ResultTypeInfo` is used to implement the `bridge_fn` macro, but can also be used outside it.
///
/// ```no_run
/// # use libsignal_bridge::node::*;
/// # use neon::prelude::*;
/// # struct Foo;
/// # impl<'a> ResultTypeInfo<'a> for Foo {
/// #     type ResultType = JsNumber;
/// #     fn convert_into(self, _cx: &mut impl Context<'a>) -> JsResult<'a, Self::ResultType> {
/// #         unimplemented!()
/// #     }
/// # }
/// # fn test(mut cx: FunctionContext) -> NeonResult<()> {
/// #     let rust_result = Foo;
/// let js_result = rust_result.convert_into(&mut cx)?;
/// #     Ok(())
/// # }
/// ```
///
/// Implementers should also see the `jni_result_type` macro in `convert.rs`.
pub trait ResultTypeInfo<'a>: Sized {
    /// The JavaScript form of the result (e.g. `JsNumber`).
    type ResultType: neon::types::Value;
    /// Converts the data in `self` to the JavaScript type, similar to `try_into()`.
    fn convert_into(self, cx: &mut impl Context<'a>) -> JsResult<'a, Self::ResultType>;
}

/// Returns `true` if `value` represents an integer within the given range.
fn can_convert_js_number_to_int(value: f64, valid_range: RangeInclusive<f64>) -> bool {
    value.is_finite() && value.fract() == 0.0 && valid_range.contains(&value)
}

// 2**53 - 1, the maximum "safe" integer representable in an f64.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
const MAX_SAFE_JS_INTEGER: f64 = 9007199254740991.0;

/// Converts non-negative numbers up to [`Number.MAX_SAFE_INTEGER`][].
///
/// [`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
impl SimpleArgTypeInfo for crate::protocol::Timestamp {
    type ArgType = JsNumber;
    fn convert_from(cx: &mut FunctionContext, foreign: Handle<Self::ArgType>) -> NeonResult<Self> {
        let value = foreign.value(cx);
        if !can_convert_js_number_to_int(value, 0.0..=MAX_SAFE_JS_INTEGER) {
            return cx.throw_range_error(format!("cannot convert {} to Timestamp (u64)", value));
        }
        Ok(Self::from_millis(value as u64))
    }
}

/// Converts non-negative numbers up to [`Number.MAX_SAFE_INTEGER`][].
///
/// [`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
impl SimpleArgTypeInfo for crate::zkgroup::Timestamp {
    type ArgType = JsNumber;
    fn convert_from(cx: &mut FunctionContext, foreign: Handle<Self::ArgType>) -> NeonResult<Self> {
        let value = foreign.value(cx);
        if !can_convert_js_number_to_int(value, 0.0..=MAX_SAFE_JS_INTEGER) {
            return cx.throw_range_error(format!("cannot convert {} to Timestamp (u64)", value));
        }
        Ok(Self::from_seconds(value as u64))
    }
}

impl SimpleArgTypeInfo for u64 {
    type ArgType = JsBuffer; // FIXME: eventually this should be a bigint
    fn convert_from(cx: &mut FunctionContext, foreign: Handle<Self::ArgType>) -> NeonResult<Self> {
        foreign
            .as_slice(cx)
            .try_into()
            .map(u64::from_be_bytes)
            .or_else(|_| cx.throw_type_error("expected a buffer of 8 big-endian bytes"))
    }
}

impl SimpleArgTypeInfo for String {
    type ArgType = JsString;
    fn convert_from(cx: &mut FunctionContext, foreign: Handle<Self::ArgType>) -> NeonResult<Self> {
        Ok(foreign.value(cx))
    }
}

impl SimpleArgTypeInfo for uuid::Uuid {
    type ArgType = JsBuffer;
    fn convert_from(cx: &mut FunctionContext, foreign: Handle<Self::ArgType>) -> NeonResult<Self> {
        uuid::Uuid::from_slice(foreign.as_slice(cx))
            .or_else(|_| cx.throw_type_error("UUIDs have 16 bytes"))
    }
}

/// Converts `null` to `None`, passing through all other values.
impl<'storage, 'context: 'storage, T> ArgTypeInfo<'storage, 'context> for Option<T>
where
    T: ArgTypeInfo<'storage, 'context>,
{
    type ArgType = JsValue;
    type StoredType = Option<T::StoredType>;
    fn borrow(
        cx: &mut FunctionContext<'context>,
        foreign: Handle<'context, Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        if foreign.downcast::<JsNull, _>(cx).is_ok() {
            return Ok(None);
        }
        let non_optional_value = foreign.downcast_or_throw::<T::ArgType, _>(cx)?;
        T::borrow(cx, non_optional_value).map(Some)
    }
    fn load_from(stored: &'storage mut Self::StoredType) -> Self {
        stored.as_mut().map(T::load_from)
    }
}

/// Converts `null` to `None`, passing through all other values.
impl<'storage, T> AsyncArgTypeInfo<'storage> for Option<T>
where
    T: AsyncArgTypeInfo<'storage>,
{
    type ArgType = JsValue;
    type StoredType = Option<T::StoredType>;
    fn save_async_arg(
        cx: &mut FunctionContext,
        foreign: Handle<Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        if foreign.downcast::<JsNull, _>(cx).is_ok() {
            return Ok(None);
        }
        let non_optional_value = foreign.downcast_or_throw::<T::ArgType, _>(cx)?;
        Ok(Some(T::save_async_arg(cx, non_optional_value)?))
    }
    fn load_async_arg(stored: &'storage mut Self::StoredType) -> Self {
        stored.as_mut().map(T::load_async_arg)
    }
}

/// Calculates a checksum to verify that a buffer wasn't mutated out from under us.
///
/// By default, this only checks the first 1024 bytes of the buffer, but it will check the entire
/// buffer if debug logging is enabled (`log::Level::Debug`).
fn calculate_checksum_for_immutable_buffer(buffer: &[u8]) -> u64 {
    let mut hasher = DefaultHasher::new();
    const LIMIT: usize = 1024;
    if log::log_enabled!(log::Level::Debug) || buffer.len() < LIMIT {
        hasher.write(buffer);
    } else {
        hasher.write(&buffer[..LIMIT]);
    }
    hasher.finish()
}

/// A wrapper around `&[u8]` that also stores a checksum, to be validated on Drop.
pub struct AssumedImmutableBuffer<'a> {
    buffer: &'a [u8],
    hash: u64,
}

impl<'a> AssumedImmutableBuffer<'a> {
    /// Loads and checksums a slice from `handle`.
    ///
    /// [A JsBuffer owns its storage][napi], so it's safe to assume the buffer won't get
    /// deallocated. What's unsafe is assuming that no one else will modify the buffer while we
    /// have a reference to it, which is why we checksum it. (We can't stop the Rust compiler from
    /// potentially optimizing out that checksum, though.)
    ///
    /// [napi]: https://nodejs.org/api/n-api.html#n_api_napi_get_buffer_info
    fn new<'b>(cx: &mut impl Context<'b>, handle: Handle<'a, JsBuffer>) -> Self {
        let buf = handle.as_slice(cx);
        let extended_lifetime_buffer = if buf.is_empty() {
            &[]
        } else {
            unsafe { extend_lifetime::<'_, 'a, [u8]>(buf) }
        };
        let hash = calculate_checksum_for_immutable_buffer(extended_lifetime_buffer);
        Self {
            buffer: extended_lifetime_buffer,
            hash,
        }
    }
}

/// Logs an error (but does not panic) if the buffer's contents have changed.
impl Drop for AssumedImmutableBuffer<'_> {
    fn drop(&mut self) {
        if self.hash != calculate_checksum_for_immutable_buffer(self.buffer) {
            log::error!("buffer modified while in use");
        }
    }
}

/// Loads from a JsBuffer, assuming it won't be mutated while in use.
/// See [`AssumedImmutableBuffer`].
impl<'storage, 'context: 'storage> ArgTypeInfo<'storage, 'context> for &'storage [u8] {
    type ArgType = JsBuffer;
    type StoredType = AssumedImmutableBuffer<'context>;
    fn borrow(
        cx: &mut FunctionContext,
        foreign: Handle<'context, Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        Ok(AssumedImmutableBuffer::new(cx, foreign))
    }
    fn load_from(stored: &'storage mut Self::StoredType) -> Self {
        stored.buffer
    }
}

/// A wrapper around a persisted JavaScript buffer and a pointer/length pair.
///
/// Like [`AssumedImmutableBuffer`], `PersistentAssumedImmutableBuffer` also stores a checksum,
/// to be validated on Finalize.
///
/// A `PersistentAssumedImmutableBuffer` **cannot be dropped**; instead, it must be explicitly
/// finalized in a JavaScript context, as it contains a [`neon::handle::Root`].
pub struct PersistentAssumedImmutableBuffer {
    owner: Root<JsBuffer>,
    buffer_start: *const u8,
    buffer_len: usize,
    hash: u64,
}

impl PersistentAssumedImmutableBuffer {
    /// Establishes a GC root for `buffer`, then loads and checksums a slice from it.
    ///
    /// [A JsBuffer owns its storage][napi], so it's safe to assume the buffer won't get
    /// deallocated. What's unsafe is assuming that no one else will modify the buffer while we
    /// have a reference to it, which is why we checksum it. (We can't stop the Rust compiler from
    /// potentially optimizing out that checksum, though.)
    ///
    /// [napi]: https://nodejs.org/api/n-api.html#n_api_napi_get_buffer_info
    fn new<'a>(cx: &mut impl Context<'a>, buffer: Handle<JsBuffer>) -> Self {
        let owner = buffer.root(cx);
        let buffer_as_slice = buffer.as_slice(cx);
        let buffer_start = if buffer_as_slice.is_empty() {
            std::ptr::null()
        } else {
            buffer_as_slice.as_ptr()
        };
        Self {
            owner,
            buffer_start,
            buffer_len: buffer_as_slice.len(),
            hash: calculate_checksum_for_immutable_buffer(buffer_as_slice),
        }
    }
}

impl Deref for PersistentAssumedImmutableBuffer {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        if self.buffer_start.is_null() {
            &[]
        } else {
            // See `new()` for the safety guarantee.
            unsafe { slice::from_raw_parts(self.buffer_start, self.buffer_len) }
        }
    }
}

// PersistentAssumedImmutableBuffer is not automatically Send because it contains a pointer.
// We're already assuming (and checking) that the contents of the buffer won't be modified
// while in use, and we know it won't be deallocated (see above).
unsafe impl Send for PersistentAssumedImmutableBuffer {}

/// Logs an error (but does not panic) if the buffer's contents have changed.
impl Finalize for PersistentAssumedImmutableBuffer {
    fn finalize<'a, C: Context<'a>>(self, cx: &mut C) {
        if self.hash != calculate_checksum_for_immutable_buffer(&*self) {
            log::error!("buffer modified while in use");
        }
        self.owner.finalize(cx)
    }
}

/// Persists the JsBuffer, assuming it won't be mutated while in use.
/// See [`PersistentAssumedImmutableBuffer`].
impl<'a> AsyncArgTypeInfo<'a> for &'a [u8] {
    type ArgType = JsBuffer;
    type StoredType = PersistentAssumedImmutableBuffer;
    fn save_async_arg(
        cx: &mut FunctionContext,
        foreign: Handle<Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        Ok(PersistentAssumedImmutableBuffer::new(cx, foreign))
    }
    fn load_async_arg(stored: &'a mut Self::StoredType) -> Self {
        &*stored
    }
}

static_assertions::assert_type_eq_all!(libsignal_protocol::Context, Option<*mut std::ffi::c_void>);
impl<'a> AsyncArgTypeInfo<'a> for *mut std::ffi::c_void {
    type ArgType = JsNull;
    type StoredType = ();
    fn save_async_arg(
        _cx: &mut FunctionContext,
        _foreign: Handle<Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        unreachable!() // only used as part of libsignal_protocol::Context
    }
    fn load_async_arg(_stored: &'a mut Self::StoredType) -> Self {
        unreachable!() // only used as part of libsignal_protocol::Context
    }
}

macro_rules! store {
    ($name:ident) => {
        paste! {
            impl<'a> AsyncArgTypeInfo<'a> for &'a mut dyn libsignal_protocol::$name {
                type ArgType = JsObject;
                type StoredType = [<Node $name>];
                fn save_async_arg(
                    cx: &mut FunctionContext,
                    foreign: Handle<Self::ArgType>,
                ) -> NeonResult<Self::StoredType> {
                    Ok(Self::StoredType::new(cx, foreign))
                }
                fn load_async_arg(stored: &'a mut Self::StoredType) -> Self {
                    stored
                }
            }
        }
    };
}

store!(IdentityKeyStore);
store!(PreKeyStore);
store!(SenderKeyStore);
store!(SessionStore);
store!(SignedPreKeyStore);

impl<'a> ResultTypeInfo<'a> for bool {
    type ResultType = JsBoolean;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        Ok(cx.boolean(self))
    }
}

/// Converts non-negative values up to [`Number.MAX_SAFE_INTEGER`][].
///
/// [`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
impl<'a> ResultTypeInfo<'a> for crate::protocol::Timestamp {
    type ResultType = JsNumber;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        let result = self.as_millis() as f64;
        if result > MAX_SAFE_JS_INTEGER {
            cx.throw_range_error(format!(
                "precision loss during conversion of {} to f64",
                self.as_millis()
            ))?;
        }
        Ok(cx.number(result))
    }
}

/// Converts non-negative values up to [`Number.MAX_SAFE_INTEGER`][].
///
/// [`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
impl<'a> ResultTypeInfo<'a> for crate::zkgroup::Timestamp {
    type ResultType = JsNumber;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        let result = self.as_seconds() as f64;
        if result > MAX_SAFE_JS_INTEGER {
            cx.throw_range_error(format!(
                "precision loss during conversion of {} to f64",
                self.as_seconds()
            ))?;
        }
        Ok(cx.number(result))
    }
}

impl<'a> ResultTypeInfo<'a> for u64 {
    type ResultType = JsBuffer; // FIXME: eventually this should be a bigint

    fn convert_into(self, cx: &mut impl Context<'a>) -> JsResult<'a, Self::ResultType> {
        let mut result = cx.buffer(8)?;
        result.as_mut_slice(cx).copy_from_slice(&self.to_be_bytes());
        Ok(result)
    }
}

impl<'a> ResultTypeInfo<'a> for String {
    type ResultType = JsString;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        self.deref().convert_into(cx)
    }
}

impl<'a> ResultTypeInfo<'a> for &str {
    type ResultType = JsString;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        Ok(cx.string(self))
    }
}

impl<'a> ResultTypeInfo<'a> for &[u8] {
    type ResultType = JsBuffer;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        let mut buffer = cx.buffer(self.len())?;
        buffer.as_mut_slice(cx).copy_from_slice(self);
        Ok(buffer)
    }
}

impl<'a> ResultTypeInfo<'a> for uuid::Uuid {
    type ResultType = JsBuffer;
    fn convert_into(self, cx: &mut impl Context<'a>) -> JsResult<'a, Self::ResultType> {
        let mut buffer = cx.buffer(16)?;
        buffer.as_mut_slice(cx).copy_from_slice(self.as_bytes());
        Ok(buffer)
    }
}

/// Converts `None` to `null`, passing through all other values.
impl<'a, T: ResultTypeInfo<'a>> ResultTypeInfo<'a> for Option<T> {
    type ResultType = JsValue;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        match self {
            Some(value) => Ok(value.convert_into(cx)?.upcast()),
            None => Ok(cx.null().upcast()),
        }
    }
}

impl<'a> ResultTypeInfo<'a> for Vec<u8> {
    type ResultType = JsBuffer;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        let mut buffer = cx.buffer(self.len())?;
        buffer.as_mut_slice(cx).copy_from_slice(&self);
        Ok(buffer)
    }
}

/// Loads from a JsBuffer, assuming it won't be mutated while in use.
/// See [`AssumedImmutableBuffer`].
impl<'storage, 'context: 'storage, const LEN: usize> ArgTypeInfo<'storage, 'context>
    for &'storage [u8; LEN]
{
    type ArgType = JsBuffer;
    type StoredType = AssumedImmutableBuffer<'context>;
    fn borrow(
        cx: &mut FunctionContext,
        foreign: Handle<'context, Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        let result = AssumedImmutableBuffer::new(cx, foreign);
        if result.buffer.len() != LEN {
            cx.throw_error(format!(
                "buffer has incorrect length {} (expected {})",
                result.buffer.len(),
                LEN
            ))?;
        }
        Ok(result)
    }
    fn load_from(stored: &'storage mut Self::StoredType) -> Self {
        stored.buffer.try_into().expect("checked length already")
    }
}

impl<'a, const LEN: usize> ResultTypeInfo<'a> for [u8; LEN] {
    type ResultType = JsBuffer;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        self.as_ref().convert_into(cx)
    }
}

impl<'a, T: ResultTypeInfo<'a>> ResultTypeInfo<'a> for NeonResult<T> {
    type ResultType = T::ResultType;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        self?.convert_into(cx)
    }
}

impl<'a> ResultTypeInfo<'a> for () {
    type ResultType = JsUndefined;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        Ok(cx.undefined())
    }
}

impl<'a, T: Value> ResultTypeInfo<'a> for Handle<'a, T> {
    type ResultType = T;
    fn convert_into(self, _cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        Ok(self)
    }
}

macro_rules! full_range_integer {
    ($typ:ty) => {
        #[doc = "Converts all valid integer values for the type."]
        impl SimpleArgTypeInfo for $typ {
            type ArgType = JsNumber;
            fn convert_from(
                cx: &mut FunctionContext,
                foreign: Handle<Self::ArgType>,
            ) -> NeonResult<Self> {
                let value = foreign.value(cx);
                if !can_convert_js_number_to_int(value, 0.0..=<$typ>::MAX.into()) {
                    return cx.throw_range_error(format!(
                        "cannot convert {} to {}",
                        value,
                        stringify!($typ),
                    ));
                }
                Ok(value as $typ)
            }
        }
        #[doc = "Converts all valid integer values for the type."]
        impl<'a> ResultTypeInfo<'a> for $typ {
            type ResultType = JsNumber;
            fn convert_into(
                self,
                cx: &mut impl Context<'a>,
            ) -> NeonResult<Handle<'a, Self::ResultType>> {
                Ok(cx.number(self as f64))
            }
        }
    };
}

full_range_integer!(u8);
full_range_integer!(u32);
full_range_integer!(i32);

impl<T> SimpleArgTypeInfo for Serialized<T>
where
    T: FixedLengthBincodeSerializable + for<'a> serde::Deserialize<'a>,
{
    type ArgType = JsBuffer;

    fn convert_from(cx: &mut FunctionContext, foreign: Handle<Self::ArgType>) -> NeonResult<Self> {
        let bytes = foreign.as_slice(cx);
        assert!(
            bytes.len() == T::Array::LEN,
            "{} should have been validated on creation",
            std::any::type_name::<T>()
        );
        let result: T = bincode::deserialize(bytes).unwrap_or_else(|_| {
            panic!(
                "{} should have been validated on creation",
                std::any::type_name::<T>()
            )
        });
        Ok(Serialized::from(result))
    }
}

impl<'a, T> crate::node::ResultTypeInfo<'a> for Serialized<T>
where
    T: FixedLengthBincodeSerializable + serde::Serialize,
{
    type ResultType = JsBuffer;

    fn convert_into(self, cx: &mut impl Context<'a>) -> JsResult<'a, Self::ResultType> {
        let result = bincode::serialize(self.deref()).expect("can always serialize a value");
        result.convert_into(cx)
    }
}

/// Extremely unsafe function to extend the lifetime of a reference.
///
/// Only here so that we're not directly calling [`std::mem::transmute`], which is even more unsafe.
/// All call sites need to explain why extending the lifetime is safe.
pub(crate) unsafe fn extend_lifetime<'a, 'b: 'a, T: ?Sized>(some_ref: &'a T) -> &'b T {
    std::mem::transmute::<&'a T, &'b T>(some_ref)
}

/// The name of the property on JavaScript objects that wrap a boxed Rust value.
pub(crate) const NATIVE_HANDLE_PROPERTY: &str = "_nativeHandle";

/// A marker trait for Rust objects exposed to JavaScript through [`neon::types::JsBox`].
///
/// Since this trait is used as a marker and implemented through a macro,
/// operations that might differ across types have been factored into the [`BridgeHandleStrategy`]
/// trait. The [`bridge_handle`] macro will automatically choose the correct strategy.
pub trait BridgeHandle: Send + Sync + Sized + 'static {
    /// Factors out operations that differ between mutable and immutable bridge handles.
    type Strategy: BridgeHandleStrategy<Self>;
}

/// Factors out operations that differ between mutable and immutable bridge handles.
///
/// See the [`BridgeHandle`] trait.
pub trait BridgeHandleStrategy<T> {
    /// If the Rust object needs any additional wrapping before being boxed by Neon,
    /// this type can be used.
    type JsBoxContents: From<T> + Send + 'static;
}

/// A phantom type for immutable bridge handles.
///
/// See [`BridgeHandleStrategy<T>`].
pub struct Immutable<T>(std::marker::PhantomData<T>);

/// A phantom type for mutable bridge handles.
///
/// See [`BridgeHandleStrategy<T>`].
pub struct Mutable<T>(std::marker::PhantomData<T>);

/// Immutable handles need no extra boxing.
impl<T: BridgeHandle> BridgeHandleStrategy<T> for Immutable<T> {
    type JsBoxContents = T;
}

/// Mutable handles use a [`RefCell`] for safety.
impl<T: BridgeHandle> BridgeHandleStrategy<T> for Mutable<T> {
    type JsBoxContents = RefCell<T>;
}

/// Shorthand for `T::Strategy::JsBoxContents` when `T: `[`BridgeHandle`], which Rust can't always
/// resolve.
type JsBoxContentsFor<T> =
    <<T as BridgeHandle>::Strategy as BridgeHandleStrategy<T>>::JsBoxContents;

impl<'a, T: BridgeHandle> ResultTypeInfo<'a> for T {
    type ResultType = JsValue;
    fn convert_into(self, cx: &mut impl Context<'a>) -> NeonResult<Handle<'a, Self::ResultType>> {
        Ok(cx
            .boxed(DefaultFinalize(JsBoxContentsFor::<T>::from(self)))
            .upcast())
    }
}
/// Used to access a boxed Rust value.
///
/// Some Rust values boxed for JavaScript are stored directly, but mutable values need to be wrapped
/// in a RefCell for safety. BorrowedJsBoxedBridgeHandle abstracts over that with the `Borrowed`
/// parameter, which represents the active borrow. For immutable values `Borrowed` will be a simple
/// reference (`&`); for mutable values it will be [`std::cell::Ref`] or [`std::cell::RefMut`].
///
/// Once created, the value can be accessed using the usual [`Deref`] and [`DerefMut`] operations.
///
/// Also stores the JavaScript handle that owns the JsBox to ensure that the Rust value is kept
/// alive.
///
/// # Example
///
/// ```no_run
/// # use libsignal_bridge::node::{BridgeHandle, BorrowedJsBoxedBridgeHandle, Mutable};
/// # use neon::prelude::*;
/// # use std::cell::{RefCell, RefMut};
/// #
/// struct Counter(usize);
/// # impl Counter { fn increment(&mut self) { self.0 += 1 } }
/// # impl BridgeHandle for Counter { type Strategy = Mutable<Counter>; };
///
/// # fn test<'a>(cx: &mut impl Context<'a>, wrapper: Handle<'a, JsObject>) -> NeonResult<()> {
/// let mut borrowed_handle: BorrowedJsBoxedBridgeHandle<RefMut<Counter>> =
///   BorrowedJsBoxedBridgeHandle::from_wrapper(cx, wrapper, RefCell::borrow_mut)?;
/// borrowed_handle.increment(); // DerefMut allows accessing the Counter inside.
/// #   Ok(())
/// # }
/// ```
pub struct BorrowedJsBoxedBridgeHandle<'a, Borrowed: Deref + 'a>
where
    Borrowed::Target: BridgeHandle,
{
    /// Keeps the data alive by functioning as an active GC reference.
    _owned: Handle<'a, DefaultJsBox<JsBoxContentsFor<Borrowed::Target>>>,
    /// Provides access to the data.
    borrowed: Borrowed,
}

impl<'a, Borrowed: Deref + 'a> BorrowedJsBoxedBridgeHandle<'a, Borrowed>
where
    Borrowed::Target: BridgeHandle,
{
    /// Creates a BorrowedJsBoxedBridgeHandle by accessing `wrapper`, assuming it does in fact
    /// reference a boxed Rust value under the `_nativeHandle` property.
    ///
    /// The `borrow` callback will be used to get an active reference for the Rust value. For
    /// example, if the "box contents" for this handle use a [`RefCell`], `borrow` might be
    /// [`RefCell::borrow_mut`] to get a mutable reference out. For a plain value, `borrow` can be
    /// the identity function.
    pub fn from_wrapper(
        cx: &mut impl Context<'a>,
        wrapper: Handle<'a, JsObject>,
        borrow: fn(&'a JsBoxContentsFor<Borrowed::Target>) -> Borrowed,
    ) -> NeonResult<Self> {
        let js_boxed_bridge_handle: Handle<'a, DefaultJsBox<JsBoxContentsFor<Borrowed::Target>>> =
            wrapper.get(cx, NATIVE_HANDLE_PROPERTY)?;
        let js_box_contents: &JsBoxContentsFor<Borrowed::Target> = &*js_boxed_bridge_handle;
        // FIXME: Workaround for https://github.com/neon-bindings/neon/issues/678
        // The lifetime of the boxed contents is necessarily longer than the lifetime of any handles
        // referring to it, i.e. longer than 'a. However, Deref'ing a Handle can only give us a
        // reference whose lifetime matches a *particular* handle. Therefore, we unsafely (in the
        // compiler sense) extend the lifetime to be the lifetime of the context, as given by the
        // Handle. (We also know the object can't move because the compiler can't know how many JS
        // references there are referring to the JsBox, any of which another thread could
        // be dereferencing.)
        let js_box_contents_with_extended_lifetime: &'a JsBoxContentsFor<Borrowed::Target> =
            unsafe { extend_lifetime(js_box_contents) };
        Ok(Self {
            _owned: js_boxed_bridge_handle,
            borrowed: borrow(js_box_contents_with_extended_lifetime),
        })
    }
}

impl<'a, Borrowed: Deref + 'a> Deref for BorrowedJsBoxedBridgeHandle<'a, Borrowed>
where
    Borrowed::Target: BridgeHandle,
{
    type Target = Borrowed::Target;

    fn deref(&self) -> &Self::Target {
        self.borrowed.deref()
    }
}

impl<'a, Borrowed: DerefMut + 'a> DerefMut for BorrowedJsBoxedBridgeHandle<'a, Borrowed>
where
    Borrowed::Target: BridgeHandle + 'static,
{
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.borrowed.deref_mut()
    }
}

/// Safely persists a boxed Rust value by treating its JavaScript wrapper as a GC root.
///
/// A `PersistentBorrowedJsBoxedBridgeHandle` **cannot be dropped**; instead, it must be explicitly
/// finalized in a JavaScript context, as it contains a [`neon::handle::Root`].
pub struct PersistentBorrowedJsBoxedBridgeHandle<T: Send + Sync + 'static> {
    owner: Root<JsObject>,
    value_ref: &'static T,
}

impl<T: BridgeHandle<Strategy = Immutable<T>>> PersistentBorrowedJsBoxedBridgeHandle<T> {
    /// Persists `wrapper`, assuming it does in fact reference a boxed Rust value under the
    /// `_nativeHandle` property.
    ///
    /// Note that this only works for immutable handles, since
    /// `PersistentBorrowedJsBoxedBridgeHandle` does not allow customizing how the box is accessed.
    pub(crate) fn new<'a>(
        cx: &mut impl Context<'a>,
        wrapper: Handle<JsObject>,
    ) -> NeonResult<Self> {
        let value_box: Handle<DefaultJsBox<T>> = wrapper.get(cx, NATIVE_HANDLE_PROPERTY)?;
        let value_ref = &***value_box;
        // We must create the root after all failable operations.
        let owner = wrapper.root(cx);
        // We're unsafely assuming that `self.owner` will maintain a reference to the JsBox
        // containing the storage referenced by `self.value_ref`.
        // N-API won't let us put a JsBox in a Root, so this indirection is necessary.
        // Once we believe the JsBox is kept alive, the safety is the same as for
        // BorrowedJsBoxedBridgeHandle.
        Ok(Self {
            owner,
            value_ref: unsafe { extend_lifetime(value_ref) },
        })
    }
}

impl<T: Send + Sync + 'static> Deref for PersistentBorrowedJsBoxedBridgeHandle<T> {
    type Target = T;
    fn deref(&self) -> &T {
        self.value_ref
    }
}

impl<T: Send + Sync + 'static> Finalize for PersistentBorrowedJsBoxedBridgeHandle<T> {
    fn finalize<'a, C: Context<'a>>(self, cx: &mut C) {
        self.owner.finalize(cx)
    }
}

/// Safely persists an array of boxed Rust values by treating the array as a GC root.
///
/// The array must contain wrapper objects that refer to an underlying `JsBox<T>`.
///
/// A `PersistentArrayOfBorrowedJsBoxedBridgeHandles` **cannot be dropped**; instead, it must be
/// explicitly finalized in a JavaScript context, as it contains a [`neon::handle::Root`].
pub struct PersistentArrayOfBorrowedJsBoxedBridgeHandles<T: Send + Sync + 'static> {
    owner: Root<JsArray>,
    // We're unsafely assuming that `owner` will the boxed references in `value_refs` alive.
    // `owner` can't be deallocated out from under us, but it could be mutated.
    value_refs: Vec<&'static T>,
}

impl<T: BridgeHandle<Strategy = Immutable<T>>> PersistentArrayOfBorrowedJsBoxedBridgeHandles<T> {
    /// Persists `array`, assuming it does in fact contain an array of objects referencing a boxed
    /// Rust value under the `_nativeHandle` property.
    ///
    /// Note that this only works for immutable handles, since
    /// `PersistentArrayOfBorrowedJsBoxedBridgeHandles` does not allow customizing how the boxes are
    /// accessed.
    pub(crate) fn new<'a>(cx: &mut impl Context<'a>, array: Handle<JsArray>) -> NeonResult<Self> {
        let len = array.len(cx);
        let value_refs = (0..len)
            .map(|i| {
                let element: Handle<JsObject> = array.get(cx, i)?;
                let value_box: Handle<DefaultJsBox<T>> = element.get(cx, NATIVE_HANDLE_PROPERTY)?;
                // We're unsafely assuming that
                // (1) the JS array will not be modified while the
                //     PersistentArrayOfBorrowedJsBoxedBridgeHandles is in use.
                // (2) each object in the array will continue to reference the JsBox we are
                //     borrowing from below.
                // Once we believe the JsBox is kept alive, the safety is the same as for
                // BorrowedJsBoxedBridgeHandle.
                Ok(unsafe { extend_lifetime::<'_, 'static, T>(&**value_box) })
            })
            .collect::<NeonResult<Vec<&'static T>>>()?;

        // We must create the root after all failable operations.
        let owner = array.root(cx);
        Ok(Self { owner, value_refs })
    }

    pub(crate) fn value_refs(&self) -> &[&T] {
        &self.value_refs
    }
}

impl<T: Send + Sync + 'static> Finalize for PersistentArrayOfBorrowedJsBoxedBridgeHandles<T> {
    fn finalize<'a, C: Context<'a>>(self, cx: &mut C) {
        self.owner.finalize(cx)
    }
}

impl<'storage, T: BridgeHandle<Strategy = Immutable<T>>> AsyncArgTypeInfo<'storage>
    for &'storage [&'storage T]
{
    type ArgType = JsArray;
    type StoredType = PersistentArrayOfBorrowedJsBoxedBridgeHandles<T>;
    fn save_async_arg(
        cx: &mut FunctionContext,
        foreign: Handle<Self::ArgType>,
    ) -> NeonResult<Self::StoredType> {
        PersistentArrayOfBorrowedJsBoxedBridgeHandles::new(cx, foreign)
    }
    fn load_async_arg(stored: &'storage mut Self::StoredType) -> Self {
        stored.value_refs()
    }
}

/// Loads a Rust value from a wrapper JavaScript object and clones it.
///
/// Note that this is currently only implemented for mutable values, where cloning protects against
/// unwanted changes. Immutable values should use borrows instead.
fn clone_from_wrapper<'a, T: BridgeHandle<Strategy = Mutable<T>> + Clone>(
    cx: &mut impl Context<'a>,
    wrapper: Handle<'a, JsObject>,
) -> NeonResult<T> {
    let borrow = BorrowedJsBoxedBridgeHandle::from_wrapper(cx, wrapper, RefCell::<T>::borrow)?;
    Ok(borrow.deref().clone())
}

/// Clones each element in an array of JavaScript wrappers around Rust values.
///
/// Note that this is currently only implemented for mutable values, where cloning protects against
/// unwanted changes. Immutable values should use borrows instead.
pub fn clone_from_array_of_wrappers<'a, T: BridgeHandle<Strategy = Mutable<T>> + Clone>(
    cx: &mut impl Context<'a>,
    array: Handle<'_, JsArray>,
) -> NeonResult<Vec<T>> {
    let len = array.len(cx);
    (0..len)
        .map(|i| {
            let wrapper: Handle<JsObject> = array.get(cx, i)?;
            clone_from_wrapper(cx, wrapper)
        })
        .collect()
}

/// Implementation of [`bridge_handle`](crate::support::bridge_handle) for Node.
macro_rules! node_bridge_handle {
    ( $typ:ty as false $(, $($_:tt)*)? ) => {};
    ( $typ:ty as $node_name:ident ) => {
        paste! {
            #[doc = "ts: interface " $typ " { readonly __type: unique symbol; }"]
            impl node::BridgeHandle for $typ {
                type Strategy = node::Immutable<Self>;
            }
        }

        // ArgTypeInfo already has a blanket impl (SimpleArgTypeInfo),
        // so we have to declare this explicitly.
        impl<'storage, 'context: 'storage> node::ArgTypeInfo<'storage, 'context>
            for &'storage $typ
        {
            type ArgType = node::JsObject;
            type StoredType = node::BorrowedJsBoxedBridgeHandle<'context, &'context $typ>;
            fn borrow(
                cx: &mut node::FunctionContext<'context>,
                foreign: node::Handle<'context, Self::ArgType>,
            ) -> node::NeonResult<Self::StoredType> {
                Self::StoredType::from_wrapper(cx, foreign, std::convert::identity)
            }
            fn load_from(stored: &'storage mut Self::StoredType) -> Self {
                &*stored
            }
        }

        // And likewise for AsyncArgTypeInfo.
        impl<'storage> node::AsyncArgTypeInfo<'storage> for &'storage $typ {
            type ArgType = node::JsObject;
            type StoredType = node::PersistentBorrowedJsBoxedBridgeHandle<$typ>;
            fn save_async_arg(
                cx: &mut node::FunctionContext,
                foreign: node::Handle<Self::ArgType>,
            ) -> node::NeonResult<Self::StoredType> {
                Self::StoredType::new(cx, foreign)
            }
            fn load_async_arg(stored: &'storage mut Self::StoredType) -> Self {
                &*stored
            }
        }
    };
    ( $typ:ty as $node_name:ident, mut = true ) => {
        paste! {
            #[doc = "ts: interface " $typ " { readonly __type: unique symbol; }"]
            impl node::BridgeHandle for $typ {
                type Strategy = node::Mutable<Self>;
            }
        }

        // ArgTypeInfo already has a blanket impl (SimpleArgTypeInfo),
        // so we have to declare these explicitly.
        impl<'storage, 'context: 'storage> node::ArgTypeInfo<'storage, 'context>
            for &'storage $typ
        {
            type ArgType = node::JsObject;
            type StoredType =
                node::BorrowedJsBoxedBridgeHandle<'context, std::cell::Ref<'context, $typ>>;
            fn borrow(
                cx: &mut node::FunctionContext<'context>,
                foreign: node::Handle<'context, Self::ArgType>,
            ) -> node::NeonResult<Self::StoredType> {
                Self::StoredType::from_wrapper(cx, foreign, std::cell::RefCell::borrow)
            }
            fn load_from(stored: &'storage mut Self::StoredType) -> Self {
                &*stored
            }
        }
        impl<'storage, 'context: 'storage> node::ArgTypeInfo<'storage, 'context>
            for &'storage mut $typ
        {
            type ArgType = node::JsObject;
            type StoredType =
                node::BorrowedJsBoxedBridgeHandle<'context, std::cell::RefMut<'context, $typ>>;
            fn borrow(
                cx: &mut node::FunctionContext<'context>,
                foreign: node::Handle<'context, Self::ArgType>,
            ) -> node::NeonResult<Self::StoredType> {
                Self::StoredType::from_wrapper(cx, foreign, std::cell::RefCell::borrow_mut)
            }
            fn load_from(stored: &'storage mut Self::StoredType) -> Self {
                &mut *stored
            }
        }
    };
    ( $typ:ty $(, mut = $_:tt)?) => {
        paste! {
            node_bridge_handle!($typ as $typ $(, mut = $_)?);
        }
    };
}
